
#ifndef AFTERMATH_RANDOM_SAMPLER_EXPONENTIAL_HPP_INCLUDED
#define AFTERMATH_RANDOM_SAMPLER_EXPONENTIAL_HPP_INCLUDED

#include "../probability/dist_exponential.hpp"
#include "ziggurat_exponential.hpp"

#include <cmath>
#include <cstdint>
#include <type_traits>

namespace aftermath
{
    namespace random
    {
        template <std::uint_fast32_t t_n_boxes, typename t_uniform_type, typename t_bounds_type, t_bounds_type t_diameter>
        struct sampler_exponential
        {
            static const std::uint_fast32_t n_boxes = t_n_boxes;
            static const t_bounds_type diameter = t_diameter;

            typedef probability::dist_exponential  distribution_type;
            typedef distribution_type::result_type result_type;
            typedef t_uniform_type                 uniform_type;
            typedef t_bounds_type                  bounds_type;
            typedef ziggurat_exponential<n_boxes, uniform_type, bounds_type, diameter> ziggurat_type;
            typedef sampler_exponential<n_boxes, uniform_type, bounds_type, diameter> type;

        private:
            double m_mu;
            ziggurat_type m_ziggurat;

        public:
            sampler_exponential()
                : sampler_exponential(distribution_type())
            {
            }

            explicit sampler_exponential(const distribution_type& distribution)
                : m_mu(distribution.expected_value()), m_ziggurat()
            {

            }

            template <typename t_engine_type>
            result_type operator ()(t_engine_type& uniform_generator)
            {
            	static_assert(std::is_same<typename t_engine_type::result_type, uniform_type>::value, "type mismatch");
                static_assert(t_engine_type::max() - t_engine_type::min() == type::diameter, "<t_engine_type>::max() - <t_engine_type>::min() has to be equal to <diameter>.");
            	
                result_type standard_random = this->m_ziggurat.sample(uniform_generator);
                return this->m_mu * standard_random;
            }
        };

        template <std::uint_fast32_t t_n_boxes, typename t_engine_type, typename t_uniform_type = typename t_engine_type::result_type>
        struct sampler_exponential_from_engine
        {
            typedef sampler_exponential<t_n_boxes, t_uniform_type, std::size_t, t_engine_type::max() - t_engine_type::min()> type;
        };
    }
}

#endif // AFTERMATH_RANDOM_SAMPLER_EXPONENTIAL_HPP_INCLUDED
